package service

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"sync/atomic"
	"time"
)

// 典型的 Option 设计模式
type Option func(*App)

// ShutdownCallback 采用 context.Context 来控制超时，而不是用 time.After 是因为
// - 超时本质上是使用这个回调的人控制的
// - 我们还希望用户知道，他的回调必须 要在一定时间内处理完毕，而且他必须显式处理超时错误
type ShutdownCallback func(ctx context.Context)

// 你需要实现这个方法
func WithShutdownCallbacks(cbs ...ShutdownCallback) Option {
	//panic("implement me")
	return func(app *App) {
		app.cbs = cbs
	}
}

// App 这里我已经预先定义好了各种可配置字段
type App struct {
	servers []*Server

	// 优雅退出整个超时时间，默认30秒
	shutdownTimeout time.Duration

	// 优雅退出时候等待处理已有请求时间，默认10秒钟
	waitTime time.Duration
	// 自定义回调超时时间，默认三秒钟
	cbTimeout time.Duration

	// 退出回调
	cbs []ShutdownCallback
}

// NewApp 创建 App 实例，注意设置默认值，同时使用这些选项
func NewApp(servers []*Server, opts ...Option) (app *App) {
	//panic("implement me")

	//	首先初始化一个APP
	app = &App{
		servers:         servers,
		shutdownTimeout: 30 * time.Second,
		waitTime:        10 * time.Second,
		cbTimeout:       3 * time.Second,
		cbs:             nil,
	}

	// 然后加载配置项，Option 是一个函数签名变量，所以直接循环导入配置就行了
	// range 单返回值时返回 index，多返回值时返回 index, value，这里需要 value
	for _, opt := range opts {
		opt(app)
	}

	return app
}

// StartAndServe 你主要要实现这个方法
func (app *App) StartAndServe() {
	for _, s := range app.servers {
		srv := s
		go func() {
			// srv 中包含一个 http.Server 在 Start() 中对这个 http.Server 调用了 ListenAndServe()
			if err := srv.Start(); err != nil {
				if err == http.ErrServerClosed {
					log.Printf("服务器%s已关闭", srv.name)
				} else {
					log.Printf("服务器%s异常退出", srv.name)
				}
			}
		}()
	}
	// 从这里开始优雅退出监听系统信号，强制退出以及超时强制退出。
	// 优雅退出的具体步骤在 shutdown 里面实现
	// 所以你需要在这里恰当的位置，调用 shutdown

	// -> 监听系统信号
	// 定义需要监听的信号
	// 参考答案里面给了。为了方便修改和扩展，是单独根据不同平台放在不同文件里面的包内全局变量
	// 首先创建一个 channel 接受系统信号
	ch := make(chan os.Signal, 2)
	// 这个地方要加 '...' 是因为要将 signals 以一个 []interface{} 的方式传入
	// 如果不加 '...' 则会将值以 []interface{[]interface{}}
	// 的方式传入
	signal.Notify(ch, signals...)
	<-ch
	println("收到退出信号")
	go func() {
		select {
		case <-ch:
			log.Printf("强制退出完成")
			os.Exit(1)
		case <-time.After(app.shutdownTimeout):
			log.Printf("退出超时，强制退出")
			os.Exit(2)
		}
	}()
	app.shutdown()
}

// shutdown 你要设计这里面的执行步骤。
func (app *App) shutdown() {
	log.Println("开始关闭应用，停止接收新请求")
	// 你需要在这里让所有的 server 拒绝新请求
	for _, s := range app.servers {
		// 思考：这里为什么我可以不用并发控制，即不用锁，也不用原子操作
		// 因为针对 serverMux.reject 只有两种状态（true 和 false），并且只有在这里才会改成 false 以拒绝请求
		// 再加上重复设置不会有任何问题，所以没有必要使用原子操作
		// 但是这样可能因为CPU的高速缓存导致一些请求没有被成功阻止，但是其实量非常少不会有大影响
		s.rejectReq()
	}

	log.Println("等待正在执行请求完结")
	// 在这里等待一段时间
	time.Sleep(app.waitTime)

	log.Println("开始关闭服务器")
	// 并发关闭服务器，同时要注意协调所有的 server 都关闭之后才能步入下一个阶段
	var wg sync.WaitGroup
	wg.Add(len(app.servers))
	for _, srv := range app.servers {
		// 同启动时候。因为要开 goroutine ，所以需要复制一份
		srvCp := srv
		go func() {
			if err := srvCp.stop(); err != nil {
				log.Printf("关闭服务%s失败\n", srvCp.name)
			}
			wg.Done()
		}()
	}
	log.Println("等待服务关闭")
	wg.Wait()
	log.Println("服务关闭完成")

	log.Println("开始执行自定义回调")
	// 并发执行回调，要注意协调所有的回调都执行完才会步入下一个阶段
	wg.Add(len(app.cbs))
	for _, cb := range app.cbs {
		c := cb
		go func() {
			ctx, cancel := context.WithTimeout(context.Background(), app.cbTimeout)
			c(ctx)
			cancel()
			wg.Done()
		}()
	}
	log.Println("等待回调执行完成")
	wg.Wait()
	log.Println("回调执行完成")

	// 释放资源
	log.Println("开始释放资源")
	app.close()
}

func (app *App) close() {
	// 在这里释放掉一些可能的资源
	time.Sleep(time.Second)
	log.Println("应用关闭")
}

// Server 代表一个 HTTP Server，一个实例监听一个端口
// 本身可以是很多种 Server，例如 http server
// 或者 RPC server
// 理论上来说，如果你设计一个脚手架的框架，那么 Server 应该是一个接口
type Server struct {
	srv  *http.Server
	name string
	mux  *serverMux
	// 请求计数
	// 这里不用 atomic.Unit64 是因为不支持 .Add(-1) 操作
	reqcnt atomic.Int64
}

// serverMux 既可以看做是装饰器模式，也可以看做委托模式
type serverMux struct {
	reject bool
	*http.ServeMux
}

func (s *serverMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 只是在考虑到 CPU 高速缓存的时候，会存在短时间的不一致性
	if s.reject {
		w.WriteHeader(http.StatusServiceUnavailable)
		_, _ = w.Write([]byte("服务已关闭"))
		return
	}
	s.ServeMux.ServeHTTP(w, r)
}

func NewServer(name string, addr string) *Server {
	mux := &serverMux{ServeMux: http.NewServeMux()}
	return &Server{
		name: name,
		mux:  mux,
		srv: &http.Server{
			Addr:    addr,
			Handler: mux,
		},
		reqcnt: atomic.Int64{},
	}
}

func (s *Server) Handle(pattern string, handler http.Handler) {
	s.reqcnt.Add(1)
	defer s.reqcnt.Add(-1)
	s.mux.Handle(pattern, handler)
}

func (s *Server) Start() error {
	return s.srv.ListenAndServe()
}

func (s *Server) rejectReq() {
	s.mux.reject = true
}

// stop 这里不用传入 ctx 进行超时控制是因为在 APP 层已经进行了一次超时控制
func (s *Server) stop() error {
	log.Printf("服务器%s关闭中", s.name)
	if s.reqcnt.Load() == 0 {
		log.Println("服务器 "+s.name+" 当前剩余 ", s.reqcnt.Load(), " 个请求未完成")
		//vTODO 这里可以改造为实时统计正在处理的请求数量，为 0 则下一步
		{
			// 一秒打印一次情况
			ticker := time.NewTicker(1 * time.Second)
			defer ticker.Stop()
			// 仿照 NewTicker 的示例
			done := make(chan struct{}, 1)

			go func() {
				for s.reqcnt.Load() > 0 {
					select {
					case <-ticker.C:
						log.Println("服务器 "+s.name+" 当前剩余 ", s.reqcnt.Load(), " 个请求未完成")
					}
				}
				done <- struct{}{}
				return
			}()

			for {
				select {
				case <-done:
					log.Printf("服务器 %s 请求处理完成\n", s.name)
					return nil
				default:
				}
			}
		}
	}

	return s.srv.Shutdown(context.Background())
}
